所有web应用程序的MVC框架都提供了确定视图的方法。Spring提供了视图解析器让你可以不需要编写特定的视图逻辑就实现模型在浏览器中的渲染。这是可以直接使用的,比如Spring可以使用JSPs,Velocity模板和XSLT模板。关于集成和使用大量不同视图技术的讨论见23章,View Technologies
在Spring处理视图时,ViewResolverView这两个接口很重要。ViewResolver提供了关于视图名和实际视图的映射。View接口负责准备请求病假请求交给一种视图技术处理。

22.5.1 Resolving views with the ViewResolver interface

正如在22.3节,"Implementing Controllers"中讨论的,Spring Web MVC控制器中的所有的处理器方法都需要解析逻辑视图名,无论是显式的(即,返回StringView,或ModelAndView)还是隐式的(即,基于转换的)。Spring中的视图有逻辑视图名映射并由视图解析器解析。Spring有不少的解析器,下面的表格列举了他们中的大部分;下面还有一些例子。

Table 22.3. View resolvers

ViewResolver Description
AbstractCachingViewResolver 可以缓存视图的抽象视图解析器。在使用之前需要被准备的视图会使用这类解析器;继承这个解析器可以获得缓存的能力。
XmlViewResolver 这个ViewResolver的实现可以接受和Spring的XML bean工厂相同的DTD的xml配置文件。默认的配置文件是/WEB-INF/views.xml
ResourceBundleViewResolver ViewResolver的实现,会使用在ResourceBundle中指定的bean定义。通常你在u类路径下定义这个properties文件。默认的文件名是views.properties
UrlBasedViewResolver ViewResolver接口的简单实现,直接将URL解析成逻辑视图名,没有隐式的映射定义。它适合你的视图资源直接符合逻辑视图名而不需要其他的映射的情况。
InternalResourceViewResolver UrlBasedViewResolver便捷的子类,支持InternalResourceView(Servlets和JSPs)和像JstlVIewTilesView这样的子类。你可以使用setViewClass(..)来指定生成所有视图的视图类。见UrlBasedViewResolver文档获取更多信息。
VelocityViewResolver/FreemarkerViewResolver UrlBasedViewResolver便捷的子类,支持VelocityView(实际上是Velocity模板)或是FreeMarkerView,以及它们的子类。
ContentNegotiatingViewResolver 实现了ViewResolver接口,基于请求的文件名或是Accept头决定接续视图。见22.5.4节,"ContentNegotiatingViewResolver"

举个例子,当你用JSP作为试图技术时,你可以使用UrlBasedViewResolver。这个试图解析器会将视图名转成URL并通过RequestDispatcher处理请求渲染视图。

<bean id="viewResolver"
        class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

当返回了test作为一个视图名是,视图解析器会将请求通过RequestDispatcher派发到/WEB-INF/jsp/test.jsp
当你在web程序中结合了多种视图技术时,你可以使用ResourceBundleViewResolver

<bean id="viewResolver"
        class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
    <property name="defaultParentView" value="parentView"/>
</bean>

ResourceBundleViewResolver监察不同基本名称的ResourceBundle和其所支持的视图解析方法,它用[viewname].(class)作为视图解析类,[viewname].url属性作为视图解析的URL。例子可以在下一节中找到。正如你所见,你可以定义一个父视图,配置文件中其他所有视图都继承自它。比如,你可以以这种方式你指定一个默认的视图类。

AbstractCachingViewResolver的子类会缓存他们所解析的视图实例。在一些视图技术中,缓存可以提高它们的性能。可以通过设置cache属性为false关闭缓存。此外,如果你必须在运行时刷新视图(比如,修改了Velocity模板),你可以使用removeFromCache(String viewName, Locale loc)方法。

22.5.2 Chaining ViewResolvers

Spring支持多种视图解析器。因此你将解析器链式调用,比如,在某些特定情况下覆盖特定视图。你可以通过添加多个解析到你的应用程序上下文中将解析器形成链式,如果需要的话,还可以由order属性指定顺序。记住,order属性越高,解析器在链中越靠后。
在下面的例子中,视图解析链由两个解析器组成,一个InternalResourceViewResolver,它会自动作为连接器链中最后的一个解析器;另一个是XmlViewResolver用来指定Excel视图。Excel视图不被InternalResourceViewResolver所支持。

<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

<bean id="excelViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
    <property name="order" value="1"/>
    <property name="location" value="/WEB-INF/views.xml"/>
</bean>

<!-- in views.xml -->

<beans>
    <bean name="report" class="org.springframework.example.ReportExcelView"/>
</beans>

如果某个特定视图解析器不符合这个视图,Spring会检测上下文中的其他视图解析器。如果还有额外的视图解析器存在,Spring会继续检查直到视图被解析。如果没有视图解析器返回视图, Spring会抛出ServletException
视图解析器的规定指定了视图解析器可以返回null表示没有找到视图。然而并不是所有的视图解析器都这么做,因为在某些情况下,解析器无法简单的探测视图是否存在。比如,InternalResourceViewResolver内部使用了RequestDispatcher,决定JSP是否存在的唯一方式是派发,但是这个动作只能执行一次。VelocityViewResolver和其他的一些也是这样。检查特定视图解析器的javadoc以查看它是否报告不存在的视图。因此,将InternalResourceViewResolver放在链中的最后一个,因为InternalResourceViewResolver总是会返回一个视图。

22.5.3 Redirecting to views

正如前面所提到的,控制器通常返回一个逻辑视图名,然后视图解析器将其解析到特定视图技术。像JSP这样的视图技术通过Servlet或是JSP引擎处理,通常是结合使用InternalResourceViewResolverInternalResourceView,内部会通过Servlet API的RequestDispatcher.forward(..)方法或RequestDispatcher.include()方法转发或是包括。对于其他的视图技术,比如Velocity,XSLT等,视图自身会将内容写入响应的流。
又是在视图被渲染之前,要先将HTTP重定向回客户端。这是合理的,比如,当POST数据调用一个控制器,但是响应实际上委托给了另一个控制器(比如,成功提交表单)。在这种情况下,通常内部的转发意味着另一个控制器也能看到相同的POST数据,如果它会和其他预期的数据混淆那么就可能产生问题。另一个在展示结构钱重定向的原因是避免用户多次提交数据。在这种情况下, 浏览器第一次会发送初始的POST;然后收到响应后重定向到不同的URL;最终浏览器会对重定向中的UR执行后续的GET请求。因此,从浏览器的角度看,页面并不反应POST的结果,而是GET的结果。最终的结果是用户无法通过执行刷新而意外重新发布相同的数据。刷新强制了对结果页的GET,而不是重发送初始POST数据。

RedirectView

强制控制器重定向的一种方式是创造并返回一个Spring的RedirectView。在这种情况下,DispatcherServlet不会使用普通的视图解析机制。由于它已经被给与了一个(重定向的)视图,DispathcerServlet只是会简单的指示视图工作。RedirectView会调用HttpServletResponse.sendRedirect()发送一个HTTP重定向到客户端浏览器。
如果你使用了RedirectView并且视图由控制器创造的,那么建议你通过注入的方式配置重定向的URL,这样它不会和控制器偶尔,而是和视图名一样由上下文配置。"The redirect:prefix"这节有助于解耦。

Passing Data To the Redirect Target

默认的,所有的模型属性应该暴露为重定向URL的URI变量。剩下的属性是原始类型或是原始类型的集合/数组,会被自动添加为查询参数。
如果专门为重定向准备了模型实例,将原始类型的属性作为查询参数是合理的。然而,在被注解的控制器中,模型可能因为渲染目的添加其他属性(比如,下拉菜单的值)。为了避免这些属性出现在URL中,@RequestMapping方法可以声明RedirectAttributes类型的属性并使用它指定发送到RedirectView的属性。如果方法发生了重定向,就会使用RedirectAttributes中的内容,否则会使用model中的内容。
RequestMappingHandlerAdapter提供了"ignoreDefaultModelOnRedirect"的标识,用来指定默认的Model属性不应该在控制器方法重定向时使用。相反的,控制器方法应该声明RedirectAttributes类型的属性或是不需要传递参数到RedirectView。为了向前兼容,MVC命名空间和MVC Java配置都将这个标识设为false,对于新的应用而言,我们建议讲它设为true
注意,当前请求中的URI模板变量会自动拓展到重定向的URL中,不需要显式的通过RedirectAttributes添加。比如:

@PostMapping("/files/{path}")
public String upload(...) {
    // ...
    return "redirect:files/{path}";
}

另一种传递数据到重定向目标的方法是通过Flash Attributes。不像其他重定向属性,flash attributes保存在HTTP session中(因此不会出现在URL中)。见22.6节,"Using flash attributes"获取更多信息。

The redirect:prefix

尽管RedirectView可以正常工作,如果控制器自身创建了RedirectView,那么就无法避免控制器知道正在发生重定向。这并不理想,因为耦合很高。控制器不应该关注响应是如何被处理的。通常,他只能操作注入其中的视图名。
redirect:前缀使你可以实现这点。如果返回的一个视图有redirect:前缀,UrlBasedViewResolver(及其所有子类)会将它识别为需要重定向的指示。视图名称的其余部分会作为重定向的URL。
它的作用和控制器返回一个RedirectView是一样的,但是现在控制器可以简单的操作视图名。像redirect:/myapp/some/resource这样的视图名会重定向到当前上下文中,然而像redirect:http://myhost.com/some/arbitrary/path会重定向到绝对URL。
注意,如果控制器处理器上有@ResponseStatus注解,注解的值优先于RedirectView这是的响应状态。

The forward:prefix

对于最终由UrlBasedViewResolver及子类解析视图也可以使用forward:前缀。这会创建一个InternalResourceView(最终实现ReqeustDispatcher.forward()),剩余的视图名部分,会被认为URL。因此,这个前缀对InternalResourceViewResolverInternalResourceView(比如JSP)不起作用。但当你使用其他的视图技术且任然希望强制使用Servlet/JSP引擎forward资源时这个前缀会很有用。(注意,你也可以使用链式视图解析器)。
redirect:前缀一样,如果带有forward:前缀的视图名称被注入到控制器中,控制器不会检测到处理响应方面的具体细节。

22.5.4 ContentNegotiatingViewResolver

ContentNegotiatingViewResolver自身不会解析视图,而是委派给其他视图解析器,选择和客户端请求相似的视图。存在两种服务器选择客户端请求表示的策略: 使用不同的URI,通常为每个不同的URI拓展名加载不同的资源。比如,http://www.eaxmaple.com/users/fred.pdf代表请求PDF,http://www.example.com/users/fred.xml请求表示了XML。
使用相同的URI来定位资源,但是HTTP请求头的Accept列举了处理的媒体类型。比如,同样都是http://www.example.com/users/fred,请求的Accept头设置为application/pdf的请求表示了PDF,而Accept头为text/xml的代表了XML。这个策略也叫content negotiation

Accept头的一个问题是无法在浏览器的HTML页面中设置它。比如,在火狐中,固定为

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

基于这种原因,开发基于浏览器的web应用时,通常会使用不同的URI来表示每个请求。

为了支持多种资源表示,Spring提供了ContentNegotiatingViewResolver来解析基于文件拓展名或是HTTP请求的Accept头的视图。ContentNegotiatingViewResolver自身不扮演视图解析的角色而是委托给通过ViewResolvers属性指定的视图解析器列表。
ContentNegotiatingViewResolver通过比较请求的媒体类型和View关联的ViewResolvers支持的没听类型(也就是Content-Type)来选择合适的View。列表中第一个Content-Type符合的View会返回其表示到客户端。如果ViewResolver链中没有符合的视图,那么会查询DefaultViews属性指定的视图列表。后者适用于可以呈现当前资源的适当表示的单例Views,而不管逻辑视图的名称如何。Accept头也可以包含通配符,比如text/*,这种情况下,Content-Type为text/xmlView也是符合的。
使用ContentNegotitatingManager可以支持自定义基于特定文件拓展名的视图解析。见22.16.6节,"Content Negotiation"
下面是一个配置ContentNegotiatingViewResolver的例子:

<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
    <property name="viewResolvers">
        <list>
            <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
            <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                <property name="prefix" value="/WEB-INF/jsp/"/>
                <property name="suffix" value=".jsp"/>
            </bean>
        </list>
    </property>
    <property name="defaultViews">
        <list>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </list>
    </property>
</bean>

<bean id="content" class="com.foo.samples.rest.SampleContentAtomView"/>

InternalResourceViewResolver处理视图名和JSP页面的转换,BeanNameViewResolver返回基于bean名称的视图。(关于Spring如何查找和初始化视图的信息见"Resolving views with the ViewResolver interface")在这个例子中,contentbean是继承自AbstractAtomFeedView的类,会返回Atom RSS回馈。关于创建Atom Feed表示的信息,见Atom Views。
上面的配置,如果一个请求带有.html的拓展名,视图解析会查找符合text/html媒体类型的视图。InternalResourceViewResolver提供了符合text/html的视图。如果一个请求带有.atom的拓展名,试图解析器会查找符合application/atom+xml的媒体类型。如果视图名返回了content,那么BeanNameViewResolver会返回映射了SampleContentAtomView的视图。如果一个请求的文件拓展名为.jsonDefaultViews列表中的MappingJackson2JsonView实例会被选择,而不管视图名是什么。客户端请求也可以不带文件拓展名而是将Accept头设置为合适的媒体类型,视图解析的方案也是一样的。

如果ContentNegotiatingViewResolverviewResolvers列表没有显式的指定,它会自动使用上下文中定义的应用程序类型。

下面显示了相应的控制器代码,它返回Atom RSS feed,其格式为http://localhost/content.atomhttp://localhost/content,并带有application/ atom+xmlAccept头。

@Controller
public class ContentController {

    private List<SampleContent> contentList = new ArrayList<SampleContent>();

    @GetMapping("/content")
    public ModelAndView getContent() {
        ModelAndView mav = new ModelAndView();
        mav.setViewName("content");
        mav.addObject("sampleContentList", contentList);
        return mav;
    }

}